home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
setl2
/
sun3.lha
/
setl2-2.2
/
etc
/
setl2.el
Wrap
Lisp/Scheme
|
1991-01-21
|
60KB
|
1,837 lines
;
; SETL2 Mode
; ==========
;
; This file contains a start at a SETL2 major mode for Gnu Emacs. The
; capabilities provided so far fall into three major areas:
;
; 1) A kind of `template' editing, in which you start typing a
; keyword, and when the macro can determine what you want to
; type, it completes the structure.
;
; 2) The ability to compile within emacs, and step forward or
; backward through the error list.
;
; 3) Some help in editing comments.
;
; Please be sure to modify the keyboard map below. I use a Sparcstation
; with a customized version of Emacs to let me remap more of the
; keyboard, and the key assignments are convenient for me but unlikely
; to work at all for others. You'll have to modify them to suit your own
; keyboard and preferences.
;
;
; Installation:
; -------------
;
; First, modify the keyboard map below. THIS IS IMPORTANT! I made no
; attempt to pick universally appropriate key assignments, and the ones
; below will override some commonly used key assignments!
;
; Second, place the modified file in the path where you keep Emacs
; macros, and preferably byte-compile it.
;
; Third, if you wish this mode to be the default for SETL2 files, then
; place the following in your .emacs file:
;
; (autoload 'setl2-mode "setl2")
; (setq auto-mode-alist (append '(("\\.stl" . setl2-mode)) auto-mode-alist))
;
; That should do it.
;
;
; Things to be done:
; ------------------
;
; There are lots of things I would like to add to this, as time permits.
; Here are a few of my ideas. If there are any Emacs LISP gurus out
; there who would like to contribute some of them I'd be grateful.
;
; 1) An execute within emacs similar to compile within emacs. On an
; abort one should be able to step forward and backward through
; the call stack.
;
; 2) A tags program for SETL2, as is provided by Gnu for C.
;
; 3) Some improvement in the CASE and IF templates.
;
;
; Implementation notes:
; ---------------------
;
; I must caution the reader of this code that I am neither a fan of nor
; an expert in LISP. I consider it a necessary evil, in order to get
; the tremendous benefits from configuring Emacs. Consequently, my
; style is likely to be quite unorthodox. Generally, I think of
; something I'd like to do, then search through the manual until I find
; the means to do it. It may not be the most elegant way, more likely
; it is the first way I found.
;
; With that warning, good luck in changing this stuff. I'm afraid some
; customization will be necessary, since several of these functions are
; not sufficiently robust.
;
;
; Syntax Table
; ------------
;
; I suspect I overuse the punctuation class here, but I don't use syntax
; classes myself so I don't know much about them. If anyone wants to
; contribute an improvement, please feel free.
;
(defvar setl2::syntax-table (make-syntax-table)
"SETL2: Syntax table"
)
(modify-syntax-entry ?_ "_" setl2::syntax-table)
(modify-syntax-entry ?\# "'" setl2::syntax-table)
(modify-syntax-entry ?\( "()" setl2::syntax-table)
(modify-syntax-entry ?\) ")(" setl2::syntax-table)
(modify-syntax-entry ?\{ "(}" setl2::syntax-table)
(modify-syntax-entry ?\} "){" setl2::syntax-table)
(modify-syntax-entry ?\[ "(]" setl2::syntax-table)
(modify-syntax-entry ?\] ")[" setl2::syntax-table)
(modify-syntax-entry ?* "." setl2::syntax-table)
(modify-syntax-entry ?/ "." setl2::syntax-table)
(modify-syntax-entry ?+ "." setl2::syntax-table)
(modify-syntax-entry ?- "." setl2::syntax-table)
(modify-syntax-entry ?= "." setl2::syntax-table)
(modify-syntax-entry ?\| "." setl2::syntax-table)
(modify-syntax-entry ?< "." setl2::syntax-table)
(modify-syntax-entry ?> "." setl2::syntax-table)
(modify-syntax-entry ?: "." setl2::syntax-table)
(modify-syntax-entry ?\; "." setl2::syntax-table)
(modify-syntax-entry ?\" "\"" setl2::syntax-table)
;
; Keymap
; ------
;
; The keymap is almost certain to be inconvenient. I use a
; Sparcstation, with a customized version of Emacs to let me use more of
; the function keys.
;
; MODIFY THIS TO SUIT YOUR PREFERENCES!!!
;
(defvar setl2::keymap (make-sparse-keymap)
"SETL2: Keymap"
)
(define-key setl2::keymap "\M-[227z" 'setl2::install-template) ; F4
(define-key setl2::keymap "\M-\t" 'setl2::next-component) ; meta-tab
(define-key setl2::keymap "\M-[228z" 'setl2::compile-buffer) ; F5
(define-key setl2::keymap "\M-[229z" 'setl2::next-error) ; F6
(define-key setl2::keymap "\M-[230z" 'setl2::previous-error) ; F7
(define-key setl2::keymap "\M-[224z" 'setl2::pull-comment) ; F1
(define-key setl2::keymap "\M-[226z" 'setl2::new-comment) ; F3
(define-key setl2::keymap "\C-k" 'setl2::wrap-in-comment) ; ^K
(define-key setl2::keymap "\C-s" 'setl2::expose-comment) ; ^S
(define-key setl2::keymap "\C-v" 'setl2::inline-comment) ; ^V
;
; The following key takes effect only in extracted comment buffers.
;
(defconst setl2::replace-comment-key "\M-[225z" ; F2
"SETL2: return from editing comment"
)
;
; This association list maps character strings to functions which open
; templates. It is used by setl2::install-template, to cut the number
; of keystrokes we have to map.
;
(defvar setl2::template-alist ()
"SETL2: map from keyword to template functions"
)
(setq setl2::template-alist '(
("program" . setl2::program-template)
("package" . setl2::package-template)
("class" . setl2::class-template)
("procedure" . setl2::procedure-template)
("lambda" . setl2::lambda-template)
("for" . setl2::for-template)
("while" . setl2::while-template)
("until" . setl2::until-template)
("loop" . setl2::loop-template)
("if" . setl2::if-template)
("case" . setl2::case-template)
))
;
; Miscellaneous other globals
;
(defconst setl2::comment-prefix "--"
"SETL2: Comment start symbol"
)
;
; setl2-mode
; ----------
;
; This function sets the mode of the current buffer to SETL2.
;
(defun setl2-mode ()
"SETL2: Major mode. Keymaps are installation-dependent"
(interactive)
(kill-all-local-variables)
(use-local-map setl2::keymap)
(setq major-mode 'setl2-mode)
(setq mode-name "SETL2")
(set-syntax-table setl2::syntax-table)
(make-local-variable 'paragraph-start)
(setq paragraph-start (concat "^$\\|" page-delimiter))
(make-local-variable 'paragraph-separate)
(setq paragraph-separate paragraph-start)
(make-local-variable 'require-final-newline)
(setq require-final-newline t)
(run-hooks 'setl2::mode-hook)
(setq tab-width 3)
(make-local-variable 'setl2::error-list)
(setq setl2::error-list '(t ()))
(make-local-variable 'setl2::next-component-list)
(setq setl2::next-component-list nil)
)
;
; Keyword Templates
; =================
;
; The basic idea of a template is that from some fairly short
; combination of keystrokes, the editor should provide a skeleton of an
; appropriate kind of program structure, and step you through filling in
; the details. So for example, ^c-f might produce:
;
; for loop
;
; end loop;
;
; Then you have to fill in the blanks yourself. The major modes for
; programming languages that I found on prep.ai.mit.edu all prompt you
; for the stuff to go in the blanks. I didn't like that, since I think
; whatever you type should go directly in the source buffer, rather than
; the minibuffer, and you should be able to take diversions between
; these components. Therefore, the functions here use a `next
; component' mechanism instead. It starts the same way, but returns
; control to you as soon as the outline is created. Then you press a
; `next component' key to move to the next blank.
;
; I also provided a somewhat unusual way to get to the original
; template. I have a terrible time remembering all the control
; sequences for most template commands, which is probably one reason I
; don't use them. Here, I use the wonderful completion mechanism in
; Emacs LISP to get something with longer control sequences, but which
; are easier to remember. Essentially, you fire up the function, and
; then start entering characters from the keyword you wish to enter.
; After each character, the macro will expand as far as it can. So the
; key for "program" would be "prg", "procedure" would be "prc" and "for"
; would be "f". Experiment a bit, typing slowly, until you get a feel
; for the abbreviations.
;
; OK, so what are the quickstart directions? First, bind the functions
; setl2::install-template and setl2::next-component to some keys (see
; above). To start a template, invoke setl2::install-template. When
; you get normal control back, enter whatever component is under the
; cursor. When finished, invoke setl2::next-component to get the next
; one.
;
; Before you start reading this stuff, let me give you a crucial
; warning: I don't use this myself, so it may be buggy. I've never been
; a fan of templates, possibly because I'm a fast typist, and I find
; that fixing things `smart' macros do wrong is more time consuming than
; entering them correctly the first time. Nevertheless, templates seem
; to be a necessary feature, probably for those who can't type quickly
; and accurately. So, here's a stab at it. If you don't like it, feel
; free to provide a superior version.
;
;
; setl2::install-template
; -----------------------
;
; This is a function to install any kind of template. It is generally
; used to provide a kind of keyboard mapping from abbreviations to
; templates. We use the template associative list to expand what the
; user gives us, until we find a unique keyword. When we find that, we
; call the function to install that specific template.
;
(defun setl2::install-template()
"SETL2: Install code template, such as for ... loop ... end loop;"
(interactive)
(let ((setl2::done-flag nil)
(setl2::abort-flag nil)
(setl2::prefix-string "")
setl2::test-string
setl2::completion
(setl2::starting-point (point-marker))
setl2::keyboard-char
(inhibit-quit t))
(message "Keyword: ")
(while (not setl2::done-flag)
;
; Get one character from the keyboard.
;
(setq setl2::keyboard-char nil)
(while (null setl2::keyboard-char)
(sit-for 1)
(cond
((>= unread-command-char 0)
(setq setl2::keyboard-char unread-command-char)
(setq unread-command-char nil)
)
(quit-flag
(setq setl2::keyboard-char ?\e)
)
((input-pending-p)
(setq setl2::keyboard-char (read-char))
)
)
)
;
; We've got one character to process. If it's an escape, we
; quit. Otherwise, we tack it onto the current prefix string
; and see if it matches something in the template list.
;
(if (eq setl2::keyboard-char ?\e)
(progn
(setq setl2::done-flag t)
(setq setl2::abort-flag t)
)
(progn
(setq setl2::keyboard-char
(char-to-string setl2::keyboard-char))
(setq setl2::test-string (concat setl2::prefix-string
setl2::keyboard-char))
(setq setl2::completion
(try-completion setl2::test-string
setl2::template-alist))
(cond
((null setl2::completion)
(message "Keyword: %s (no match for %s)"
setl2::prefix-string
setl2::test-string)
(ding)
)
((eq setl2::completion t)
(setq setl2::prefix setl2::test-string)
(setq setl2::done-flag t)
)
(t
(if (eq (try-completion setl2::completion
setl2::template-alist) t)
(progn
(setq setl2::done-flag t)
(setq setl2::prefix-string setl2::completion)
)
(progn
(insert (substring setl2::completion
(length setl2::prefix-string)))
(setq setl2::prefix-string setl2::completion)
(message "Keyword: %s" setl2::prefix-string)
)
)
)
)
)
)
)
;
; We have to reset the global quit flag, or emacs will goof up on
; return.
;
(setq quit-flag nil)
;
; We've been inserting stuff directly in the source buffer. Now
; get rid of it, and let the template function decide what to
; enter.
;
(kill-region setl2::starting-point (point))
(if (not setl2::abort-flag)
(funcall (cdr (assoc setl2::prefix-string setl2::template-alist)))
)
)
)
;
; setl2::next-component
; ---------------------
;
; This function should jump the user to the next component of the
; current template. The next-component-list consists of either markers
; or functions, along with arguments (usually more markers). We call
; all functions, discard null markers, and stop at the first marker
; still in the source buffer.
;
(defun setl2::next-component ()
"SETL2: Move cursor to the next component of the active template"
(interactive)
(let ((setl2::done-flag nil)
(setl2::error-flag t)
(setl2::component))
;
; We loop until we exhaust the list, or we find an active marker.
;
(while (not setl2::done-flag)
(cond
((null setl2::next-component-list)
(setq setl2::done-flag t)
(if setl2::error-flag
(progn
(message "No pending program structures!")
(ding)
)
)
)
((listp (car setl2::next-component-list))
(setq setl2::component (car setl2::next-component-list))
(setq setl2::next-component-list
(cdr setl2::next-component-list))
(funcall (car setl2::component)
(cdr setl2::component))
(setq setl2::error-flag nil)
)
((markerp (car setl2::next-component-list))
(if (null (marker-position (car setl2::next-component-list)))
(setq setl2::next-component-list
(cdr setl2::next-component-list))
(progn
(goto-char (marker-position
(car setl2::next-component-list)))
(setq setl2::next-component-list
(cdr setl2::next-component-list))
(setq setl2::error-flag nil)
(setq setl2::done-flag t)
)
)
)
(t
(setq setl2::next-component-list
(cdr setl2::next-component-list))
)
)
)
)
)
;
; Template Macros
; ===============
;
; The following macros insert the actual templates into the source
; buffer. The previous stuff was control functions, which just call
; these.
;
;
; setl2::program-template
; -----------------------
;
; The program, package, class, and procedure templates all have some
; logic to plant the unit name in the tail, so the user never has to
; type `end <program name>;'. Here we open up a program and plant the
; procedure to fill in the tail.
;
(defun setl2::program-template ()
"SETL2: Template => program ... ; ... end <name>;"
(interactive)
(let ((setl2::starting-point (point))
(setl2::structure-column (current-column))
(setl2::marker-list ()))
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert "program ")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert ";\n\n\n\n\n")
(previous-line 3)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "end")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert ";")
(setq setl2::next-component-list
(append
(list
(list 'setl2::insert-unit-tail
"program[ \\t]*\\([^ \\t;]+\\)[ \\t]*;"
1
(nth 3 setl2::marker-list)
(nth 0 setl2::marker-list))
(list 'setl2::column-marker
(nth 1 setl2::marker-list)
(+ setl2::structure-column tab-width)))
setl2::next-component-list))
(goto-char (marker-position (nth 2 setl2::marker-list)))
)
)
;
; setl2::package-template
; -----------------------
;
; The program, package, class, and procedure templates all have some
; logic to plant the unit name in the tail, so the user never has to
; type `end <package name>;'. Here we open up a package and plant the
; procedure to fill in the tail.
;
(defun setl2::package-template ()
"SETL2: Template => package ... ; ... end <name>;"
(interactive)
(let ((setl2::starting-point (point))
(setl2::structure-column (current-column))
(setl2::marker-list ()))
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert "package ")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert ";\n\n\n\n\n")
(previous-line 3)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "end")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert ";")
(setq setl2::next-component-list
(append
(list
(list 'setl2::insert-unit-tail
"package[ \\t]*\\(body[ \\t]*\\)?\\([^ \\t;]+\\)[ \\t]*;"
2
(nth 3 setl2::marker-list)
(nth 0 setl2::marker-list))
(list 'setl2::column-marker
(nth 1 setl2::marker-list)
(+ setl2::structure-column tab-width)))
setl2::next-component-list))
(goto-char (marker-position (nth 2 setl2::marker-list)))
)
)
;
; setl2::class-template
; ---------------------
;
; The program, package, class, and procedure templates all have some
; logic to plant the unit name in the tail, so the user never has to
; type `end <class name>;'. Here we open up a class and plant the
; procedure to fill in the tail.
;
(defun setl2::class-template ()
"SETL2: Template => class ... ; ... end <name>;"
(interactive)
(let ((setl2::starting-point (point))
(setl2::structure-column (current-column))
(setl2::marker-list ()))
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert "class ")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert ";\n\n\n\n\n")
(previous-line 3)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "end")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert ";")
(setq setl2::next-component-list
(append
(list
(list 'setl2::insert-unit-tail
"class[ \\t]*\\(body[ \\t]*\\)?\\([^ \\t;]+\\)[ \\t]*;"
2
(nth 3 setl2::marker-list)
(nth 0 setl2::marker-list))
(list 'setl2::column-marker
(nth 1 setl2::marker-list)
(+ setl2::structure-column tab-width)))
setl2::next-component-list))
(goto-char (marker-position (nth 2 setl2::marker-list)))
)
)
;
; setl2::procedure-template
; -------------------------
;
; The program, package, class, and procedure templates all have some
; logic to plant the unit name in the tail, so the user never has to
; type `end <procedure name>;'. Here we open up a procedure and plant the
; procedure to fill in the tail.
;
(defun setl2::procedure-template ()
"SETL2: Template => procedure ... (...) ; ... end <name>;"
(interactive)
(let ((setl2::starting-point (point))
(setl2::structure-column (current-column))
(setl2::marker-list ()))
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert "procedure ")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert ";\n\n\n\n\n")
(previous-line 3)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "end")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert ";")
(setq setl2::next-component-list
(append
(list
(list 'setl2::insert-unit-tail
"procedure[ \\t]*\\([^ \\t(;]+\\)[^;]*;"
1
(nth 3 setl2::marker-list)
(nth 0 setl2::marker-list))
(list 'setl2::column-marker
(nth 1 setl2::marker-list)
(+ setl2::structure-column tab-width)))
setl2::next-component-list))
(goto-char (marker-position (nth 2 setl2::marker-list)))
)
)
;
; setl2::insert-unit-tail
; ------------------------
;
; This function fills in the unit name from the header, assuming the
; programmer didn't move anything.
;
(defun setl2::insert-unit-tail (args)
"SETL2: Ending name for program, package, ..."
(let ((setl2::starting-point (point))
(setl2::head-pattern (nth 0 args))
(setl2::match-number (nth 1 args))
(setl2::unit-head (nth 2 args))
(setl2::unit-tail (nth 3 args)))
(if (not (null (marker-position setl2::unit-head)))
(progn
(goto-char (marker-position setl2::unit-head))
(if (and (looking-at setl2::head-pattern)
(not (null (marker-position setl2::unit-tail))))
(progn
(goto-char (marker-position setl2::unit-tail))
(insert " ")
(insert (buffer-substring
(match-beginning setl2::match-number)
(match-end setl2::match-number)))
)
)
)
)
(goto-char setl2::starting-point)
)
)
;
; setl2::lambda-template
; ----------------------
;
; The lambda template is easier than the other procedure-like templates.
; We don't have to worry about a name.
;
(defun setl2::lambda-template ()
"SETL2: Template => lambda; ... end lambda;"
(interactive)
(let ((setl2::starting-point (point))
(setl2::structure-column (current-column))
(setl2::marker-list ()))
(insert "lambda")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert ";\n\n\n\n\n")
(previous-line 3)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "end lambda;")
(setq setl2::next-component-list
(append
(list
(list 'setl2::column-marker
(car setl2::marker-list)
(+ setl2::structure-column tab-width)))
setl2::next-component-list))
(goto-char (marker-position (nth 1 setl2::marker-list)))
)
)
;
; setl2::for-template
; -------------------
;
; The loop templates are all relatively straightforward. We only need
; to plant a marker for the body.
;
(defun setl2::for-template ()
"SETL2: Template => for ... loop ... end loop;"
(interactive)
(let ((setl2::starting-point (point))
(setl2::structure-column (current-column))
(setl2::marker-list ()))
(insert "for ")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert " loop\n\n\n\n\n")
(previous-line 3)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "end loop;")
(setq setl2::next-component-list
(append
(list
(list 'setl2::column-marker
(car setl2::marker-list)
(+ setl2::structure-column tab-width)))
setl2::next-component-list))
(goto-char (marker-position (nth 1 setl2::marker-list)))
)
)
;
; setl2::while-template
; ---------------------
;
; The loop templates are all relatively straightforward. We only need
; to plant a marker for the body.
;
(defun setl2::while-template ()
"SETL2: Template => while ... loop ... end loop;"
(interactive)
(let ((setl2::starting-point (point))
(setl2::structure-column (current-column))
(setl2::marker-list ()))
(insert "while ")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert " loop\n\n\n\n\n")
(previous-line 3)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "end loop;")
(setq setl2::next-component-list
(append
(list
(list 'setl2::column-marker
(car setl2::marker-list)
(+ setl2::structure-column tab-width)))
setl2::next-component-list))
(goto-char (marker-position (nth 1 setl2::marker-list)))
)
)
;
; setl2::until-template
; ---------------------
;
; The loop templates are all relatively straightforward. We only need
; to plant a marker for the body.
;
(defun setl2::until-template ()
"SETL2: Template => until ... loop ... end loop;"
(interactive)
(let ((setl2::starting-point (point))
(setl2::structure-column (current-column))
(setl2::marker-list ()))
(insert "until ")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert " loop\n\n\n\n\n")
(previous-line 3)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "end loop;")
(setq setl2::next-component-list
(append
(list
(list 'setl2::column-marker
(car setl2::marker-list)
(+ setl2::structure-column tab-width)))
setl2::next-component-list))
(goto-char (marker-position (nth 1 setl2::marker-list)))
)
)
;
; setl2::loop-template
; --------------------
;
; The loop templates are all relatively straightforward. We only need
; to plant a marker for the body.
;
(defun setl2::loop-template ()
"SETL2: Template => loop ... end loop;"
(interactive)
(let ((setl2::starting-point (point))
(setl2::structure-column (current-column))
(setl2::marker-list ()))
(insert "loop\n\n\n\n\n")
(previous-line 3)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "end loop;")
(goto-char (marker-position (car setl2::marker-list)))
(setl2::move-to-column (+ setl2::structure-column tab-width))
)
)
;
; setl2::if-template
; ------------------
;
; I have a severe problem with the if and case templates. The problem
; is that both have a variable number of clauses, elseif for if
; statements and when for case. I could use separate templates, but
; that seems like too much to invoke them. So, for now, I'm just going
; to give a bunch of the clauses, on the assumption that deleting a
; block of text is painless but adding clauses is not.
;
(defun setl2::if-template ()
"SETL2: Template => if .. then .. else .. end if;"
(interactive)
(let ((setl2::starting-point (point))
(setl2::structure-column (current-column))
(setl2::marker-list ()))
(insert "if ")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert " then\n\n\n\n\n\n\n\n\n\n\n\n")
(previous-line 10)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "elseif ")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert " then")
(next-line 2)
(beginning-of-line)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "else")
(next-line 2)
(beginning-of-line)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "end if;")
(setq setl2::next-component-list
(append
(list
(list 'setl2::column-marker
(nth 3 setl2::marker-list)
(+ setl2::structure-column tab-width))
(nth 2 setl2::marker-list)
(list 'setl2::column-marker
(nth 1 setl2::marker-list)
(+ setl2::structure-column tab-width))
(list 'setl2::column-marker
(nth 0 setl2::marker-list)
(+ setl2::structure-column tab-width)))
setl2::next-component-list))
(goto-char (marker-position (nth 4 setl2::marker-list)))
)
)
;
; setl2::case-template
; --------------------
;
; I have a severe problem with the if and case templates. The problem
; is that both have a variable number of clauses, elseif for if
; statements and when for case. I could use separate templates, but
; that seems like too much to invoke them. So, for now, I'm just going
; to give a bunch of the clauses, on the assumption that deleting a
; block of text is painless but adding clauses is not.
;
(defun setl2::case-template ()
"SETL2: Template => case ... end case;"
(interactive)
(let ((setl2::starting-point (point))
(setl2::structure-column (current-column))
(setl2::marker-list ()))
(insert "case ")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert "\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
(previous-line 12)
(setl2::move-to-column (+ setl2::structure-column tab-width))
(insert "when ")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert " =>")
(next-line 2)
(beginning-of-line)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column (+ setl2::structure-column tab-width))
(insert "when ")
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(insert " =>")
(next-line 2)
(beginning-of-line)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column (+ setl2::structure-column tab-width))
(insert "otherwise =>")
(next-line 2)
(beginning-of-line)
(setq setl2::marker-list
(cons (point-marker) setl2::marker-list))
(next-line 2)
(setl2::move-to-column setl2::structure-column)
(insert "end case;")
(setq setl2::next-component-list
(append
(list
(nth 4 setl2::marker-list)
(list 'setl2::column-marker
(nth 3 setl2::marker-list)
(+ setl2::structure-column (* tab-width 2)))
(nth 2 setl2::marker-list)
(list 'setl2::column-marker
(nth 1 setl2::marker-list)
(+ setl2::structure-column (* tab-width 2)))
(list 'setl2::column-marker
(nth 0 setl2::marker-list)
(+ setl2::structure-column (* tab-width 2))))
setl2::next-component-list))
(goto-char (marker-position (nth 5 setl2::marker-list)))
)
)
;
; setl2::column-marker
; --------------------
;
; I don't trust markers appearing after the last non-blank column. It's
; too easy to remove them. Therefore, in situations where I'd like to
; plant such a column, I plant one at the beginning of a line instead
; and keep my desired column number. A marker at the beginning of a
; line is much more likely to be kept.
;
(defun setl2::column-marker (args)
(let ((setl2::line-marker (nth 0 args))
(setl2::column-number (nth 1 args)))
(if (not (null (marker-position setl2::line-marker)))
(progn
(goto-char (marker-position setl2::line-marker))
(move-to-column setl2::column-number)
(if (< (current-column) setl2::column-number)
(insert (make-string
(- setl2::column-number (current-column))
(string-to-char " ")))
)
(setq setl2::next-component-list
(cons (point-marker) setl2::next-component-list))
)
)
)
)
;
; setl2::move-to-column
; ---------------------
;
; This function moves the cursor to a given column, inserting spaces if
; necessary. The result is that the cursor REALLY moves to the desired
; column, whether or not something was there previously.
;
(defun setl2::move-to-column (setl2::column)
(move-to-column setl2::column)
(if (and (not buffer-read-only)
(< (current-column) setl2::column))
(insert (make-string (- setl2::column (current-column))
(string-to-char " ")))
)
)
;
; Compile functions
; =================
;
; One of the more useful features of a programming language major mode
; is the ability to compile the current buffer, and step forward and
; back through the error messages. This is most useful on short
; projects, consisting of a single source file (it tends to break down
; when one needs makefiles).
;
;
; setl2::compile_buffer
; ---------------------
;
; This function compiles the current buffer. It first saves the buffer,
; if necessary, executes the SETL2 compiler and gathers up the error
; messages in the list. The following two functions allow the user to
; step forward and backward through that list.
;
(defun setl2::compile-buffer ()
(interactive)
(let ((setl2::source-name buffer-file-name)
(setl2::source-buffer (current-buffer))
(setl2::listing-buffer (generate-new-buffer "*errors*"))
setl2::error-line
setl2::error-column
setl2::error-text)
;
; Save the setl2 file if necessary.
;
(save-buffer)
;
; Compile the current buffer
;
(setq setl2::error-list ())
(switch-to-buffer setl2::listing-buffer)
(shell-command (concat "stlc -n " setl2::source-name) t)
;
; Read through the errors, gathering them into a list
;
(beginning-of-buffer)
(while (re-search-forward "\\[\\([0-9]*\\):\\([0-9]*\\)\\]" nil t)
(setq setl2::error-line
(string-to-int (buffer-substring
(match-beginning 1) (match-end 1))))
(setq setl2::error-column
(string-to-int (buffer-substring
(match-beginning 2) (match-end 2))))
(re-search-forward "\\([^ ].*\\)$" nil t)
(setq setl2::error-text (buffer-substring
(match-beginning 1) (match-end 1)))
(switch-to-buffer setl2::source-buffer)
(goto-line setl2::error-line)
(setl2::move-to-column (- setl2::error-column 1))
(setq setl2::error-list (cons (list (point-marker) setl2::error-text)
setl2::error-list))
(switch-to-buffer setl2::listing-buffer)
)
(switch-to-buffer setl2::source-buffer)
(setq setl2::error-list (cons t (reverse (cons nil setl2::error-list))))
(kill-buffer setl2::listing-buffer)
(if (null (nth 1 setl2::error-list))
(message "Compilation Successful")
(setl2::next-error)
)
)
)
;
; setl2::next-error
; -----------------
;
; This function finds the next remaining error in the error list and
; displays it. It assumes that the original error list consists of a
; direction flag, a list of errors, and a nil to indicate the end of the
; list.
;
(defun setl2::next-error ()
(interactive)
(let ((setl2::last-was-forward (car setl2::error-list))
setl2::displayed-error)
;
; Strip off the direction flag and switch directions, if
; necessary.
;
(if setl2::last-was-forward
(setq setl2::error-list (cdr setl2::error-list))
(setq setl2::error-list
(reverse (cons (nth 1 setl2::error-list)
(reverse (nthcdr 2 setl2::error-list)))))
)
;
; Strip away any error messages whose position has been deleted
;
(while (and (not (null (car setl2::error-list)))
(null (marker-position (car (car setl2::error-list)))))
(setq setl2::error-list (cdr setl2::error-list))
)
;
; We either have a valid error to display, or there are no new
; ones.
;
(if (null (car setl2::error-list))
(progn
(message "No more errors")
(setq setl2::error-list (cons t setl2::error-list))
)
(progn
(setq setl2::displayed-error (car setl2::error-list))
(setq setl2::error-list
(cons t (reverse (cons setl2::displayed-error
(reverse (cdr setl2::error-list))))))
(goto-char (marker-position (car setl2::displayed-error)))
(message (car (cdr setl2::displayed-error)))
)
)
)
)
;
; setl2::previous-error
; ---------------------
;
; This function finds the previous remaining error in the error list and
; displays it. It assumes that the original error list consists of a
; direction flag, a list of errors, and a nil to indicate the end of the
; list.
;
(defun setl2::previous-error ()
(interactive)
(let ((setl2::last-was-forward (car setl2::error-list))
setl2::displayed-error)
;
; Strip off the direction flag and switch directions, if
; necessary.
;
(if setl2::last-was-forward
(setq setl2::error-list
(reverse (cons (car (reverse setl2::error-list))
(reverse
(cdr (reverse (cdr setl2::error-list)))))))
(setq setl2::error-list (reverse (cdr setl2::error-list)))
)
;
; Strip away any error messages whose position has been deleted
;
(while (and (not (null (car setl2::error-list)))
(null (marker-position (car (car setl2::error-list)))))
(setq setl2::error-list (cdr setl2::error-list))
)
;
; We either have a valid error to display, or there are no new
; ones.
;
(if (null (car setl2::error-list))
(progn
(message "No more errors")
(setq setl2::error-list (cons nil (reverse setl2::error-list)))
)
(progn
(setq setl2::displayed-error (car setl2::error-list))
(setq setl2::error-list
(cons nil (cons setl2::displayed-error
(reverse (cdr setl2::error-list)))))
(goto-char (marker-position (car setl2::displayed-error)))
(message (car (cdr setl2::displayed-error)))
)
)
)
)
;
; Comments
; ========
;
; Like every programmer, I have a preferred comment style. This section
; facilitates editing comments in that style. If you're using this
; package, but have a different comment style, you'll probably want to
; delete this section completely and write your own comment support
; functions.
;
; My style is to use block comments at the beginning of procedures or
; sections within a procedure. I use relatively few in-line comments.
; To be honest, the comments I use are meant to provide large markers
; around procedures as much as to convey useful information. Lots of
; times the drivel I put there could have been inferred just as easily
; from the procedure name.
;
; I like to use word processing mode to edit the comment text. The
; following procedures are designed to pick the text out of the
; surrounding boilerplate and to replace the text within new
; boilerplate. The boilerplate is usually just a bunch of lines with --
; in the same column. There are two optional things which can appear as
; well, though. First, I sometimes format comments as TeX source (I
; have other macros which 'texify' files, and copy such comments
; intact). These begin with --\. There may also be a form feed on the
; first line. All of this stuff is ignored when pulling a comment, but
; restored when the text is replaced.
;
;
; setl2::pull-comment
; -------------------
;
; This is the entry point for grabbing the text out of a comment. We
; make sure there is an appropriate comment, then call
; setl2::pull-comment-here to do the actual grabbing. This saves a lot
; of duplication of code with setl2::new-comment.
;
(defun setl2::pull-comment ()
(interactive)
(if (null (search-forward setl2::comment-prefix nil t))
(message "No comment found")
(progn
(backward-char (length setl2::comment-prefix))
(setl2::pull-comment-here)
)
)
)
;
; setl2::new-comment
; ------------------
;
; This is the entry point for making a new comment, in word processing
; style. We just insert the comment and call setl2::pull-comment-here.
;
(defun setl2::new-comment ()
(interactive)
(insert setl2::comment-prefix)
(backward-char (length setl2::comment-prefix))
(setl2::pull-comment-here)
)
;
; setl2::pull-comment-here
; ------------------------
;
; This is the guts of the comment-pulling procedure. There are a lot of
; things to do here, so just follow the step-by-step comments.
;
(defun setl2::pull-comment-here ()
(let ((setl2::main-buffer (current-buffer))
(setl2::comment-position (point-marker))
(setl2::comment-column (current-column))
(setl2::TeX-flag nil)
(setl2::form-feed-flag nil)
setl2::comment-line
(setl2::comment-block ())
setl2::comment-line-begin
(setl2::blank-columns 99999))
;
; First, we collect the lines of the comment into a (reversed)
; list. We pay attention to suffixes "\\" and "\f", and keep
; track of the minimum number of blanks after the prefix, as well.
;
(while (and (equal (current-column) setl2::comment-column)
(looking-at (regexp-quote setl2::comment-prefix)))
(forward-char (length setl2::comment-prefix))
(setq setl2::comment-line "")
(if (looking-at "\\\\")
(progn
(setq setl2::TeX-flag t)
(forward-char 1)
(setq setl2::comment-line (concat setl2::comment-line " "))
)
)
(if (looking-at "\f")
(progn
(setq setl2::form-feed-flag t)
(forward-char 1)
(setq setl2::comment-line (concat setl2::comment-line ""))
)
)
(if (looking-at "[ \t]*$")
(setq setl2::comment-line "")
(progn
(setq setl2::comment-line-begin (point))
(end-of-line)
(untabify setl2::comment-line-begin (point))
(setq setl2::comment-line
(concat setl2::comment-line
(buffer-substring setl2::comment-line-begin
(point))))
(setq setl2::blank-columns
(min setl2::blank-columns
(string-match "[^ ]" setl2::comment-line)))
)
)
(setq setl2::comment-block (cons setl2::comment-line
setl2::comment-block))
(next-line 1)
(move-to-column setl2::comment-column)
)
;
; Now setl2::comment-block has the text of the comment as a list
; of lines. We have to create a word-processing buffer for it.
;
(switch-to-buffer (generate-new-buffer "*comment*"))
(beginning-of-buffer)
(while (not (null setl2::comment-block))
(setq setl2::comment-line (car setl2::comment-block))
(if (equal (length setl2::comment-line) 0)
(insert "\n")
(progn
(insert setl2::comment-line)
(insert "\n")
(beginning-of-buffer)
(move-to-column setl2::blank-columns)
(kill-region (point-min) (point))
)
)
(setq setl2::comment-block (cdr setl2::comment-block))
(beginning-of-buffer)
)
;
; We've inserted the lines into the buffer, so we're ready to do
; some beautification. We remove trailing spaces and leading
; blank lines.
;
(beginning-of-buffer)
(replace-regexp " +$" "")
(beginning-of-buffer)
(while (and (< (point) (point-max))
(looking-at "$"))
(delete-char 1)
)
;
; We have to save some stuff in buffer-local variables, for use by
; the comment replacement function.
;
(make-local-variable 'setl2::save-main-buffer)
(setq setl2::save-main-buffer setl2::main-buffer)
(make-local-variable 'setl2::save-comment-position)
(setq setl2::save-comment-position setl2::comment-position)
(make-local-variable 'setl2::save-TeX-flag)
(setq setl2::save-TeX-flag setl2::TeX-flag)
(make-local-variable 'setl2::save-form-feed-flag)
(setq setl2::save-form-feed-flag setl2::form-feed-flag)
;
; Now we establish word processing mode. The real work here is
; done by Emacs' auto-fill mode, all we do is call it, and set up
; a key to return us to the source buffer.
;
(auto-fill-mode 1)
(setq fill-column (- 70 setl2::comment-column))
(make-local-variable 'setl2::comment-keymap)
(if (not (null (current-local-map)))
(setq setl2::comment-keymap (copy-keymap (current-local-map)))
(progn
(setq setl2::comment-keymap (make-sparse-keymap))
(use-local-map setl2::comment-keymap)
)
)
(local-set-key setl2::replace-comment-key 'setl2::replace-comment)
)
)
;
; setl2::replace-comment
; ----------------------
;
; This function replaces text extracted from a comment. Like the
; comment-pulling procedure, there are a lot of things to do here, so
; follow the step-by-step comments.
;
(defun setl2::replace-comment ()
(interactive)
(let ((setl2::comment-buffer (current-buffer))
(setl2::main-buffer setl2::save-main-buffer)
(setl2::comment-position setl2::save-comment-position)
(setl2::TeX-flag setl2::save-TeX-flag)
(setl2::form-feed-flag setl2::save-form-feed-flag)
setl2::comment-line
setl2::comment-column
(setl2::comment-block ())
setl2::comment-line-begin
setl2::done-flag
setl2::save-position
setl2::block-flag)
;
; First we do some buffer conditioning. We remove tabs, trailing
; spaces, and leading and trailing blank lines.
;
(untabify (point-min) (point-max))
(beginning-of-buffer)
(replace-regexp " +$" "")
(beginning-of-buffer)
(while (and (< (point) (point-max))
(looking-at "$"))
(delete-char 1)
)
(end-of-buffer)
(insert "\n\n\n\n")
(if (re-search-backward "[^ \t\n]" nil t)
(kill-region (+ (point) 1) (point-max))
(kill-region (point-min) (point-max))
)
(end-of-buffer)
(insert "\n")
(previous-line 1)
;
; Now we gather up the lines into a list. This is an ugly loop,
; since LISP doesn't let me break out of a while loop (at least
; in my meager knowledge of LISP!).
;
(beginning-of-line)
(setq setl2::done-flag nil)
(while (not setl2::done-flag)
(setq setl2::comment-line-begin (point))
(end-of-line)
(if (equal (point) setl2::comment-line-begin)
(setq setl2::comment-block (cons "" setl2::comment-block))
(setq setl2::comment-block
(cons (buffer-substring setl2::comment-line-begin (point))
setl2::comment-block))
)
(beginning-of-line)
(if (equal (point) (point-min))
(setq setl2::done-flag t)
(previous-line 1)
)
)
;
; We've got our comment text gathered up again, so we switch back
; to the source buffer. We've got to look out for the user
; deleting either the source buffer or the marker, but fortunately
; the marker-position function handles both cases.
;
(if (null (marker-position setl2::comment-position))
(message "Comment's original position has disappeared!")
;
; The original position is still there, so go to it.
;
(progn
(switch-to-buffer setl2::main-buffer)
(goto-char setl2::comment-position)
;
; Clear out the old comment text.
;
(setq setl2::comment-column (current-column))
(while (and (equal (current-column) setl2::comment-column)
(looking-at (regexp-quote setl2::comment-prefix)))
(forward-char (length setl2::comment-prefix))
(if (not (looking-at "[ \t]*$"))
(kill-line)
)
(next-line 1)
(move-to-column setl2::comment-column)
)
;
; Insert new text, as long as we still have the old comment
; prefixes.
;
(goto-char setl2::comment-position)
(forward-char (length setl2::comment-prefix))
(if setl2::TeX-flag
(insert "\\")
)
(if setl2::form-feed-flag
(insert "\f")
)
(goto-char setl2::comment-position)
(next-line 1)
(move-to-column setl2::comment-column)
(while (and (equal (current-column) setl2::comment-column)
(looking-at (regexp-quote setl2::comment-prefix))
(not (null setl2::comment-block)))
(forward-char (length setl2::comment-prefix))
(if setl2::TeX-flag
(insert "\\")
)
(insert " ")
(insert (car setl2::comment-block))
(setq setl2::comment-block (cdr setl2::comment-block))
(next-line 1)
(move-to-column setl2::comment-column)
)
;
; We ran out of comment text, or old prefixes.
;
(if (not (null setl2::comment-block))
;
; The prefixes gave out first. We insert new lines,
; lining up with the old prefixes.
;
(progn
(previous-line 1)
(while (not (null setl2::comment-block))
(end-of-line)
(insert "\n")
(insert (make-string setl2::comment-column
(string-to-char " ")))
(insert setl2::comment-prefix)
(if setl2::TeX-flag
(insert "\\")
)
(insert " ")
(insert (car setl2::comment-block))
(setq setl2::comment-block (cdr setl2::comment-block))
)
(end-of-line)
(insert "\n")
(insert (make-string setl2::comment-column
(string-to-char " ")))
(insert setl2::comment-prefix)
(if setl2::TeX-flag
(insert "\\")
)
)
;
; We ran out of comment text, but have some prefixes
; left.
;
(progn
(forward-char (length setl2::comment-prefix))
(if setl2::TeX-flag
(insert "\\")
)
(next-line 1)
;
; Check whether any of the remaining prefixes have
; text in front of them. If they do, we leave the
; lines there but remove the prefixes. Otherwise we
; delete the lines altogether.
;
(move-to-column setl2::comment-column)
(setq setl2::save-position (point))
(setq setl2::block-flag t)
(while (and (equal (current-column) setl2::comment-column)
(looking-at
(regexp-quote setl2::comment-prefix))
setl2::block-flag)
(beginning-of-line)
(if (looking-at (concat "[ \t]*" setl2::comment-prefix))
(setq setl2::block-flag nil)
)
)
(goto-char setl2::save-position)
(while (and (equal (current-column) setl2::comment-column)
(looking-at
(regexp-quote setl2::comment-prefix)))
(if setl2::block-flag
(progn
(beginning-of-line)
(kill-line 1)
(move-to-column setl2::comment-column)
)
(progn
(kill-line)
(next-line 1)
(move-to-column setl2::comment-column)
)
)
)
)
)
;
; We're done with the comment buffer.
;
(kill-buffer setl2::comment-buffer)
(goto-char setl2::comment-position)
)
)
)
)
;
; setl2::wrap-in-comment
; ----------------------
;
; One of the nice things about some versions of C is that one can easily
; comment out a large chunk of a program by placing the beginning and
; ending delimiter. This causes as many difficulties as benefits,
; though, since some compilers won't nest comments, and those which do
; get very confused when one forgets an ending delimiter. It seems more
; socially acceptable therefore to use prefix comments, which start with
; some marker and extend to the end of a line. SETL2 follows this
; convention.
;
; A little help from an editor macro can give one the ability to comment
; out a large block of a SETL2 program, just as we can with C. This
; function just inserts the comment symbol on all lines between point
; and mark, inclusive. The companion function `setl2::expose-comment'
; removes these comment symbols and reactivates the enclosed code.
;
(defun setl2::wrap-in-comment ()
(interactive)
(let (setl2::start-position setl2::first-line setl2::count)
(setq setl2::start-position (point-marker))
(if (< (point) (mark))
(progn
(setq setl2::first-line (point))
(setq setl2::count (count-lines (point) (mark)))
)
(progn
(setq setl2::first-line (mark))
(setq setl2::count (count-lines (mark) (point)))
)
)
(goto-char setl2::first-line)
(beginning-of-line)
(while (>= setl2::count 0)
(insert setl2::comment-prefix)
(next-line 1)
(beginning-of-line)
(setq setl2::count (- setl2::count 1))
)
(goto-char (marker-position setl2::start-position))
)
)
;
; setl2::expose-comment
; ----------------------
;
; This is the companion to `setl2::wrap-in-comment'. It exposes the
; text previously hidden.
;
(defun setl2::expose-comment ()
(interactive)
(let (setl2::start-position setl2::first-line setl2::last-line)
(setq setl2::start-position (point-marker))
(if (< (point) (mark))
(progn
(setq setl2::first-line (point))
(setq setl2::count (count-lines (point) (mark)))
)
(progn
(setq setl2::first-line (mark))
(setq setl2::count (count-lines (mark) (point)))
)
)
(goto-char setl2::first-line)
(beginning-of-line)
(while (>= setl2::count 0)
(if (looking-at (regexp-quote setl2::comment-prefix))
(delete-char (length setl2::comment-prefix))
)
(next-line 1)
(beginning-of-line)
(setq setl2::count (- setl2::count 1))
)
(if (not (null (marker-position setl2::start-position)))
(goto-char (marker-position setl2::start-position))
)
)
)
;
; setl2::inline-comment
; ---------------------
;
; I rarely use in-line comments, except for one situation -- to explain
; the meaning of a variable. In that case I use something like this:
;
; var x; -- never use x
;
; I always start these things in column 40. This macro just makes it
; easy to get out there.
;
(defun setl2::inline-comment ()
(interactive)
(end-of-line)
(if (> (current-column) 40)
(insert "\n")
)
(insert (make-string (- 40 (current-column))
(string-to-char " ")))
(insert setl2::comment-prefix)
(insert " ")
)